// SPDX-License-Identifier: GPL-2.0-only

#define pr_fmt(fmt) "quick_kexec: " fmt

#include <linux/memblock.h>
#include <linux/ioport.h>
#include <linux/types.h>
#include <linux/kexec.h>
#ifdef CONFIG_EULEROS_ANGEL_CORE_MONITOR
#include <asm/angel_core.h>
#include <linux/cpu.h>
#endif

#include "internal.h"

void __init reserve_quick_kexec(void)
{
	int ret;
	struct resource *quick_kexec_res;
	unsigned long long mem_len = 0;
	unsigned long long mem_start = 0;

	ret = parse_quick_kexec(boot_command_line, memblock_phys_mem_size(),
			&mem_len, &mem_start);
	if (ret || !mem_len)
		return;

	mem_start = memblock_find_in_range(CRASH_ALIGN,
			CRASH_ADDR_LOW_MAX,
			mem_len,
			CRASH_ALIGN);
	if (mem_start == 0) {
		pr_info("quick kexec reservation failed - memory is in use.\n");
		return;
	}

	ret = memblock_reserve(mem_start, mem_len);
	if (ret) {
		pr_err("%s: Error reserving quick kexec memblock.\n", __func__);
		return;
	}

	pr_info("Reserving %ldMB of memory for quick kexec\n",
			(unsigned long)(mem_len >> 20));
	quick_kexec_res = get_quick_kexec_res();
	quick_kexec_res->start = mem_start;
	quick_kexec_res->end = mem_start + mem_len - 1;
	insert_resource(&iomem_resource, quick_kexec_res);
}

void __init reserve_quick_rollback(void)
{
	int ret;
	struct resource *res;
	char *res_name = QUICK_ROLLBACK_RES_NAME;
	unsigned long long mem_len, mem_start;

	ret = parse_quick_rollback(boot_command_line, memblock_phys_mem_size(),
		&mem_len, &mem_start);
	if (ret || !mem_len)
		return;

	res = get_kexec_extend_res(res_name);
	if (res) {
		set_quick_rollback_res(res);
		return;
	}

	if (get_kexec_extend_res_space(&res))
		return;

	mem_start = memblock_find_in_range(CRASH_ALIGN,
				CRASH_ADDR_LOW_MAX,
				mem_len,
				CRASH_ALIGN);
	if (mem_start == 0) {
		pr_err("quick rollback reservation failed - memory is in use.\n");
	goto out;
	}
	ret = memblock_reserve(mem_start, mem_len);
	if (ret) {
		pr_err("%s: Error reserving quick rollback memblock.\n", __func__);
		goto out;
	}
	res->start = mem_start;
	res->end = mem_start + mem_len - 1;
	res->name = res_name;
	res->flags = IORESOURCE_MEM;
	res->desc = IORES_DESC_QUICK_KEXEC;
	insert_resource(&iomem_resource, res);
	set_quick_rollback_res(res);
	return;
out:
	free_kexec_extend_res_space();
}

#ifdef CONFIG_EULEROS_ANGEL_CORE_MONITOR
void __init reserve_angel_mem(void)
{
	int ret;
	char *res_name = ANGEL_MONITOR_RES_NAME;
	struct resource *res;
	unsigned long long angel_mem_phys = 0;
	unsigned long long angel_mem_len = 0;

	ret = parse_angel_mem(boot_command_line, memblock_phys_mem_size(),
			&angel_mem_len, &angel_mem_phys);
	if (ret || !angel_mem_len)
		return;

	res = get_kexec_extend_res(res_name);
	if (res) {
		memblock_remove(res->start + PAGE_SIZE, PAGE_SIZE);
		set_angel_mem_area(res->start, res->end - res->start + 1);
		init_angel_monitor_core(res->start);
		return;
	}

	if (get_kexec_extend_res_space(&res))
		return;

	if (angel_mem_len < ANGEL_MEM_MIN_SIZE) {
		pr_info("angel section mem size too small.\n");
		goto out;
	}
	angel_mem_phys = memblock_find_in_range(CRASH_ALIGN,
				CRASH_ADDR_LOW_MAX,
				angel_mem_len,
				CRASH_ALIGN);
	if (angel_mem_phys == 0) {
		pr_warn("angel_mem_phys reservation failed - memory is in use.\n");
		goto out;
	}
	ret = memblock_reserve(angel_mem_phys, angel_mem_len);
	if (ret) {
		pr_err("%s: Error reserving angel_mem_phys memblock.\n", __func__);
		goto out;
	}
	pr_info("angel mem reserved success\n");
#ifndef CONFIG_KASAN
	memblock_remove(angel_mem_phys + PAGE_SIZE, PAGE_SIZE);
#endif
	res->start = angel_mem_phys;
	res->end = angel_mem_phys + angel_mem_len - 1;
	res->name = res_name;
	res->flags = IORESOURCE_MEM;
	res->desc = IORES_DESC_QUICK_KEXEC;
	insert_resource(&iomem_resource, res);
	set_angel_mem_area(res->start, angel_mem_len);
	init_angel_monitor_core((unsigned long)angel_mem_phys);
	return;
out:
	free_kexec_extend_res_space();
}
#endif

#ifdef CONFIG_EULEROS_X86_CPU_PARK
# define CPU_PARK_ADDR_LOW_MAX	(896UL << 20)

/* Physical address of reserved park memory. */
unsigned long park_start;
/* Virtual address of reserved park memory. */
unsigned long park_start_virt;
/* park reserve mem len should be PAGE_SIZE * NR_CPUS */
unsigned long park_len;
bool cpu_park_enable;
bool park_boot_disable;
struct resource *cpu_park_res;
int cpuparkmem_set;

static int __init parse_park_mem(char *p)
{
	park_len = PARK_SECTION_SIZE;
	return 0;
}
early_param("cpuparkmem", parse_park_mem);

void __init reserve_park_mem(void)
{
	struct resource *res;
	char *res_name = CPU_PARK_RES_NAME;

	if (park_len == 0)
		return;

	res = get_kexec_extend_res(res_name);
	if (res && park_len) {
		park_start = res->start;
		memblock_remove(park_start + park_len - PAGE_SIZE, PAGE_SIZE);
		cpu_park_res = res;
		cpu_park_enable = true;
		return;
	}

	if (get_kexec_extend_res_space(&res))
		return;

	park_len = PAGE_ALIGN(park_len);
	park_start = memblock_find_in_range(CRASH_ALIGN,
				CPU_PARK_ADDR_LOW_MAX,
				park_len,
				CRASH_ALIGN);
	if (park_start == 0) {
		pr_err("cpu park mem reservation failed - memory is in use.\n");
		goto out;
	}

	memblock_reserve(park_start, park_len);
#ifndef CONFIG_KASAN
	memblock_remove(park_start + park_len - PAGE_SIZE, PAGE_SIZE);
#endif
	pr_info("cpu park mem reserved successfully\n");

	res->start = park_start;
	res->end = park_start + park_len - 1;
	res->name = res_name;
	res->flags = IORESOURCE_BUSY | IORESOURCE_SYSTEM_RAM;
	res->desc = IORES_DESC_NONE;
	insert_resource(&iomem_resource, res);
	cpu_park_res = res;
	cpu_park_enable = true;
	return;
out:
	pr_info("cpu park space reserve failed, cpu park disabled");
	free_kexec_extend_res_space();
	park_start = 0;
	park_len = 0;
	cpu_park_enable = false;
}
#endif
